#ifndef __SELECT_SVR_H__
#define __SELECT_SVR_H__

#include <iostream>
#include <sys/select.h>
#include "Log.hpp"
#include "Sock.hpp"

#define BITS 8
#define NUM (sizeof(fd_set) * BITS)
#define FD_NONE -1

using namespace std;
// select 这里只完成读取，写入和异常不做处理 -- epoll(写完整)
class SelectServer
{
public:
    SelectServer(const uint16_t &port = 8080)
        : _port(port)
    {
        _listensock = Sock::Socket();
        Sock::Bind(_listensock, _port);
        Sock::Listen(_listensock);
        logMessage(DEBUG, "%s", "create base socket success");
        for(int i = 0; i < NUM; i++) 
        {
            _fd_array[i] = FD_NONE;
        }
        _fd_array[0] = _listensock; // 规定 : _fd_array[0] = _listensock;
    }

    void Start()
    {
        while (true)
        {
            // // 测试select的第五个参数
            // fd_set rfds;
            // FD_ZERO(&rfds);
            // struct timeval timeout = {0, 0}; // 输入输出型，后面会变成0,
            // // 如何看待listensock -> 获取新连接，我们把它依旧看做成为IO，input事件，如果没有连接到来呢 -> 阻塞
            // // int sock = Sock::Accept(listensock, ...); //不能直接调用accept了
            // FD_SET(_listensock, &rfds); // 将listensock添加到读文件描述符集中
            // int n = select(_listensock + 1, &rfds, nullptr, nullptr, nullptr);

            // 1. nfds: 随着获取的sock越来越多，添加到select的sock越来越多，注定了nfds每一次都可能要变化,所以需要对它动态计算
            // 2. rfds/writefds/exceptfds：都是输入输出型参数，输入输出不一定是一样的，所以我们每一次都要对rfds进行重新添加
            // 上两点 -> 必须自己将合法的文件描述符需要单独全部保存起来 用来支持：1.更新最大fd 2.更新位图结构 -> 所以定义数组
            DebugPrint();
            fd_set rfds;
            FD_ZERO(&rfds); // 每次都清零
            int maxfd = _listensock;
            for (int i = 0; i < NUM; i++) // 1.更新最大fd 2.更新位图结构
            {
                if (_fd_array[i] == FD_NONE)
                    continue;
                FD_SET(_fd_array[i], &rfds);
                if (maxfd < _fd_array[i])
                    maxfd = _fd_array[i];
            }
            // rfds未来，一定会有两类sock，listensock，普通sock，select中，而且就绪的fd会越来越多
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
            switch (n)
            {
            case 0:
                // printf("hello select ...\n");
                logMessage(DEBUG, "%s", "time out...");
                break;
            case -1:
                logMessage(WARNING, "select error: %d : %s", errno, strerror(errno));
                break;
            default:
                // 成功的
                logMessage(DEBUG, "get a new link event...");
                HandlerEvent(rfds);
                break;
            }
        }
    }
    ~SelectServer()
    {
        if (_listensock >= 0)
            close(_listensock);
    }
private:
    void HandlerEvent(const fd_set &rfds) // fd_set 是一个集合，里面可能会存在多个sock
    {
        for (int i = 0; i < NUM; i++)
        {
            if (_fd_array[i] == FD_NONE) // 1. 去掉不合法的fd
                continue;
            // 2. 合法的fd不一定就绪了
            if (FD_ISSET(_fd_array[i], &rfds)) // 如果fd就绪
            {
                if (_fd_array[i] == _listensock) // 读事件就绪：连接事件到来，accept
                {
                    Accepter();
                }
                else  // 读事件就绪：INPUT事件到来，read / recv
                {
                    Recver(i);
                    logMessage(DEBUG, "message in, get IO event: %d", _fd_array[i]);
                    sleep(1);
                }
            }
        }
    }
    void Accepter()
    {
        string clientip;
        uint16_t clientport = 0;
        // listensock上面的读事件就绪了，表示可以读取了，获取新连接了
        int sock = Sock::Accept(_listensock, &clientip, &clientport); // 这里进行accept不会阻塞
        if (sock < 0)
        {
            logMessage(WARNING, "accept error");
            return;
        }
        logMessage(DEBUG, "get a new line success : [%s:%d] : %d", clientip.c_str(), clientport, sock);
        // 不能read / recv ，因为我们不清楚该sock上面数据什么时候到来， recv、read就有可能先被阻塞，IO = 等+数据拷贝
        // 谁可能最清楚该sock上面数据什么时候到来 -> select
        // 得到新连接的时候，应该考虑的是，将新的sock托管给select，让select帮我们进行检测sock上是否有新的数据
        // 有了数据select，读事件就绪，select就会通知我，我们再进行读取，此时就不会被阻塞了
        // 要将sock添加 给 select，只要将fd放入到数组中即可
        int pos = 1; // 规定了 _fd_array[0] = _listensock; 不用管
        for(;pos < NUM; pos++) // 将fd放入到数组中 -> 找一个合法的位置
        {
            if (_fd_array[pos] == FD_NONE)
                break;
        }
        if (pos == NUM) // 满了不能加了
        {
            logMessage(WARNING, "%s:%d", "select server already full, close: %d", sock);
            close(sock);
        }
        else // 找到了合法的位置
        {
            _fd_array[pos] = sock;
        }
    }
    void Recver(int pos)
    {
        // 读事件就绪：INPUT事件到来、recv，read
        logMessage(DEBUG, "message in, get IO event: %d", _fd_array[pos]);
        // 此时select已经帮我们进行了事件检测，fd上的数据一定是就绪的，即 本次 不会被阻塞
        // 怎么保证以读到了一个完整的报文呢？ -> 模拟实现epoll的时候再考虑
        char buffer[1024];
        int n = recv(_fd_array[pos], buffer, sizeof(buffer)-1, 0);
        if(n > 0) // 正常读取
        {
            buffer[n] = 0;
            logMessage(DEBUG, "client[%d]# %s", _fd_array[pos], buffer);
        }
        else if(n == 0) // 对端把链接关了
        {
            logMessage(DEBUG, "client[%d] quit, me too...", _fd_array[pos]);
            close(_fd_array[pos]); // 关闭不需要的fd
            _fd_array[pos] = FD_NONE; // 不要让select帮我关心当前的fd了
        }
        else // 读取错误
        {
            logMessage(WARNING, "%d sock recv error, %d : %s", _fd_array[pos], errno, strerror(errno));
            close(_fd_array[pos]); // 关闭不需要的fd
            _fd_array[pos] = FD_NONE; // 不要让select帮我关心当前的fd了
        }
    }
    void DebugPrint() // 打印一下数组里合法的fd
    {
        cout << "_fd_array[]: ";
        for (int i = 0; i < NUM; i++)
        {
            if (_fd_array[i] != FD_NONE)
                cout << _fd_array[i] << " ";
        }
        cout << endl;
    }
private:
    uint16_t _port;
    int _listensock;
    int _fd_array[NUM]; // 可以用vector，但现在看看select的缺点
    // int _fd_write[NUM]; 这里就不写了
};

#endif